#Copyright (c) 2012-8 Civa Lin. All rights reserved.
"""
Database Access 資料庫工具模組

本模組提供 sqlite 的支援，是官方 sqlite3 模組的補充。
"""

import sqlite3
import functools
import pickle
import zlib
import datetime
import uuid

def extend_type_support():
    '''擴展資料庫支援格式

        包括：
            DATETIME, DATE, TIME 等時間物件。
            UUID 物件。
            LIST, TUPLE, SETS, DICT 等容器物件。

        無需引數，直接對全域發生效果。
    '''
    ### 資料庫中常見物件 ###
    # DATETIME
    sqlite3.register_adapter(datetime.datetime, pickle.dumps)
    sqlite3.register_converter('DATETIME', pickle.loads)
    # DATE
    sqlite3.register_adapter(datetime.date, pickle.dumps)
    sqlite3.register_converter('DATE', pickle.loads)
    # TIME
    sqlite3.register_adapter(datetime.time, pickle.dumps)
    sqlite3.register_converter('TIME', pickle.loads)
    # UUID
    sqlite3.register_adapter(uuid.UUID, pickle.dumps)
    sqlite3.register_converter('UUID', pickle.loads)
    ### 容器物件 ###
    # LIST
    sqlite3.register_adapter(list, pickle.dumps)
    sqlite3.register_converter('LIST', pickle.loads)
    # TUPLE
    sqlite3.register_adapter(tuple, pickle.dumps)
    sqlite3.register_converter('TUPLE', pickle.loads)
    # DICT
    sqlite3.register_adapter(dict, pickle.dumps)
    sqlite3.register_converter('DICT', pickle.loads)
    # SETS : 群集，不用 set 是為了避開 update x set... 的 sql 語句。
    sqlite3.register_adapter(set, pickle.dumps)
    sqlite3.register_converter('SETS', pickle.loads)

def regfunc(conn):
    '''註冊常用的 sql func 到 sqlite3.connect 物件

        conn = 註冊的對象

        常用 func 包括……

        in_con(container, thing) -> bool
            偵測 thing 是否在 container 中
            container = TUPLE, LIST, SETS, DICT 等容器（DICT 偵測的是 key）
            thing     = 任何要檢查是否在容器中的物件
        '''
    conn.create_function(
        'in_con', 2,
        lambda container, thing: thing in pickle.loads(container))

class sql:
    '''常用的 sql 語句集合

        用 .format() 替換關鍵字後送入 conn.execute(...) 執行，範例如下：
            s = sql.query.format(
                columns = 'id', table = 'book', cond = 'name like :name')
            conn.execute(s, {'name': '%game%'})

        變數：
            table   = 要操作的表格名稱：
                        'book'
            columns = 要操作的複數欄位名稱：
                        'id, name, key'
            values  = 要指派的複數變數值。順序要和 columns 相等：
                        ':id, :name, :key'
            assign  = 值指派給欄位的語句：
                        'name = :name, id = :id'
            cond    = 語句的執行條件：
                        'id = :id'
            declare = 表格格式聲明：
                        'id TEXT, name TEXT, PRIMARY KEY(id)'
            version = 使用者自定義的資料庫版本號（整數字串）
                        '12'
            op_bool = 難以歸類的真假值操作。
                        'ON'
    '''
    __slots__ = ()

    # 查詢資料
    query = 'SELECT {columns} FROM {table} WHERE {cond};'
    query_all = 'SELECT {columns} FROM {table};'
    # Row 變動
    insert = 'INSERT INTO {table} ( {columns} ) VALUES( {values} );'
    update = 'UPDATE {table} SET {assign} WHERE {cond};'
    delete = 'DELETE FROM {table} WHERE {cond};'
    # 表格變動
    create = 'CREATE TABLE {table} ( {declare} );'
    drop = 'DROP TABLE {table};'
    truncate = 'TRUNCATE TABLE {table};' # 刪光表中的內容但留下表
    # 資料庫維護
    vacuum = 'VACUUM;'
    dbver = 'PRAGMA user_version;'
    dbver_set = 'PRAGMA user_version = {version};'
    check = 'PRAGMA integrity_check;'
    table_info = 'PRAGMA table_info( {table} );'
    # 透過 table_info 取出 table 資料，
    # 每一 row 的內容為： cid|name|type|notnull|dflt_value|pk

def insql(conn, table_name: str) -> str:
    '''對指定的表，產生一個字典型 sql 插入字串'''
    def _column_names(conn, table_name: str) -> tuple:
        '''取回指定資料表的欄位名稱列表

            本列表會依照順序排列
            '''
        query = sql.table_info.format(table = table_name)
        rows = conn.execute(query).fetchall()
        return tuple(row[1] for row in rows)

    def _addcolon(column_names: tuple) -> tuple:
        '''每個元素前方加上冒號'''
        return tuple(':' + name for name in column_names)

    def _names_args(column_names: tuple) -> str:
        '''將用 tuple 表示的 column_names 轉換為引數字串格式

            如：
                tuple('id','name','page')) -> ':id, :name, :page'
            '''
        return functools.reduce(
                lambda x, y: x + ', ' + y,
                _addcolon(column_names))

    args = _names_args( _column_names(conn, table_name) )
    return 'INSERT INTO {} values({});'.format(table_name, args)

class pickz:
    '''將 pickle 與 zlib 兩個步驟合而為一'''
    __slots__ = ()

    def z(obj):
        '''先 pickle.dumps 再 zlib.compress'''
        return zlib.compress(pickle.dumps(obj))

    def unz(zobj):
        '''先 zlib.decompress 再 zlib.loads'''
        return pickle.loads(zlib.decompress(gzobj))

